/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: result.h
 *
 * Purpose: This section describes the ara::core::Result<T, E> type 
 *          (and its specialization for T=void) that contains a 
 *          value of type T or an error of type E
 *
 * Developer:
 *   wen.gu , 2020-04-08
 *
 * TODO:
 *
 ***************************************************************************/

#ifndef __ARA_CORE_RESULT_H__
#define __ARA_CORE_RESULT_H__
/******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#include "ara/core/error_code.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/


/******************************************************************************
 **    TYPE DEFINITIONS
 ******************************************************************************/
namespace ara
{
namespace core
{
/******************************************************************************
 **    CLASSES/FUNCTIONS DEFINITIONS
 ******************************************************************************/
template <typename T, typename E = ErrorCode>
class Result final 
{
public:
    using value_type = T;
    using error_type = E;

public:
    Result(T const& t)
    :mValue(new value_type(t))
    {
        /** todo something */
    }

    Result(T&& t)
    :mValue(new value_type(std::move(t)))
    {
        /** todo something */
    }

    Result(E const& e)
    :mErr(new error_type(e))
    {
        /** todo something */
    }

    Result(E&& e)
        :mErr(new error_type(std::move(e)))
    {
        /** todo something */
    }

    Result(Result const& other)
    {
        if (other.mValue)
        {
            mValue = new value_type(*other.mValue);
        }

        if (other.mErr)
        {
            mErr = new error_type(*other.mErr);
        }
    }

    Result(Result&& other) noexcept(std::is_nothrow_move_constructible< T >::value && 
                                    std::is_nothrow_move_constructible< E >::value)
        :mValue(other.mValue),
        mErr(other.mErr)
    {
        other.mValue = nullptr;
        other.mErr = nullptr;
    }

    ~Result()
    {
        if (mValue)
        {
            delete mValue;
        }

        if (mErr)
        {
            delete mErr;
        }
    }

public:
    static Result FromValue(T const& t)
    {
        return std::move(Result(t));
    }

    static Result FromValue(T && t)
    {
        return std::move(Result(std::move(t)));
    }

    template <typename... Args>
    static typename std::enable_if<std::is_constructible<T, Args...>::value, Result >::type FromValue(Args&&... args)
    {
        //static_assert(std::is_constructible<value_type, args...>);

        return std::move(Result(value_type(args...)));
    }

    static Result FromError(E const& e)
    {
        return std::move(Result(e));
    }

    static Result FromError(E && e)
    {
        return Result(std::move(e));
    }


    template <typename... Args>
    static typename std::enable_if<std::is_constructible<E, Args...>::value, Result>::type FromError(Args&&... args)
    {
        /*static_assert(std::is_constructible<error_type, args...>::value);*/

        return std::move(Result(E(args...)));
    }

public:
    Result& operator= (Result const& other)
    {
        if (this != std::addressof(other))
        {
            if (mValue)
            {
                delete mValue;
                mValue = nullptr;
            }

            if (mErr)
            {
                delete mErr;
                mErr = nullptr;
            }

            if (other.mValue)
            {
                mValue = new value_type(*other.mValue);
            }

            if (other.mErr)
            {
                mErr = new error_type(*other.mErr);
            }
        }

        return *this;
    }

    Result& operator= (Result&& other) noexcept(std::is_nothrow_move_constructible<T>::value && 
                                                std::is_nothrow_move_assignable<T>::value && 
                                                std::is_nothrow_move_constructible<E>::value && 
                                                std::is_nothrow_move_assignable<E>::value)
    {
        if (this != std::addressof(other))
        {
            if (mValue)
            {
                delete mValue;
                mValue = nullptr;
            }

            if (mErr)
            {
                delete mErr;
                mErr = nullptr;
            }

            mValue = other.mValue;
            mErr = other.mErr;

            other.mValue = nullptr;
            other.mErr = nullptr;
        }

        return *this;
    }

    template <typename... Args>
    typename std::enable_if<std::is_constructible<T, Args...>::value, void>::type EmplaceValue(Args&&... args)
    {
        if (mValue)
        {
            delete mValue;
        }

        mValue = new value_type(args...);
    }

    template <typename... Args>
    typename std::enable_if<std::is_constructible<E, Args...>::value, void>::type EmplaceError(Args&&... args)
    {
        if (mErr)
        {
            delete mErr;
        }

        mErr = new error_type(args...);
    }

    void Swap(Result& other) noexcept(std::is_nothrow_move_constructible< T >::value && 
                                      std::is_nothrow_move_assignable< T >::value && 
                                      std::is_nothrow_move_constructible< E>::value && 
                                      std::is_nothrow_move_assignable< E >::value)
    {
        value_type* tempVal = other.mValue;
        error_type* tempErr = other.mErr;

        other.mValue = mValue;
        other.mErr = mErr;
        mValue = tempVal;
        mErr = tempErr;
    }

public:
    
    explicit operator bool() const noexcept
    {
        return mValue != nullptr;
    }

    T const& operator * () const&
    {
        /**the spec says(so here we just do assert):
          * The behavior of this function is undefined if *this
          * does not contain a value.
          */
        assert(mValue != nullptr);

        return *mValue;
    }

    T&& operator * ()&&
    {
        /**the spec says(so here we just do assert):
          * The behavior of this function is undefined if *this
          * does not contain a value.
          */
        assert(mValue != nullptr);
        value_type* ptmp = mValue;
        mValue = nullptr;

        return std::move(*ptmp);
    }

    T const* operator-> () const
    {
        /**the spec says(so if haven't value, here we just return nullptr):
        * The behavior of this function is undefined if *this
        * does not contain a value.
        */
        return mValue;
    }

public:
    bool HasValue() const noexcept
    {
        return mValue != nullptr;
    }

    T const& Value() const&
    {
        /**the spec says(so here we just do assert):
         * The behavior of this function is undefined if *this
         * does not contain a value.
         */
        assert(mValue != nullptr);

        return *mValue;
    }

    T&& Value()&&
    {
        /**the spec says(so here we just do assert):
        * The behavior of this function is undefined if *this
        * does not contain a value.
        */
        assert(mValue != nullptr);
        value_type* ptmp = mValue; /** todo refine me?? */
        mValue = nullptr;

        return std::move(*ptmp);
    }

    E const& Error() const&
    {
        /**the spec says(so here we just do assert):
        * The behavior of this function is undefined if *this
        * does not contain a value.
        */
        assert(mErr != nullptr);

        return *mErr;
    }

    E&& Error()&&
    {
        /**the spec says(so here we just do assert):
         * The behavior of this function is undefined if *this
         * does not contain a value.
         */
        assert(mErr != nullptr);
        error_type* ptmp = mErr; /** todo refine me?? */
        mErr = nullptr;

        return std::move(*ptmp);
    }

    template <typename U>
    T ValueOr(U&& defaultValue) const&
    {
        if (mValue)
        {
            return *mValue;
        }

        return static_cast<value_type>(defaultValue);
    }

    template <typename U>
    T ValueOr(U&& defaultValue)&&
    {
        if (mValue)
        {
            value_type* ptmp = mValue; /** todo refine me?? */
            mValue = nullptr;

            return std::move(*ptmp);
        }

        return std::move(static_cast<T>(defaultValue));
    }

    template <typename G>
    E ErrorOr(G&& defaultError) const
    {
        if (mErr)
        {
            return *mErr;
        }

        return static_cast<E>(defaultError);
    }

    template <typename G>
    bool CheckError(G&& error) const
    {
        if (mErr)
        {
            return *mErr == static_cast<E>(error);
        }

        return false;
    }

    T const& ValueOrThrow() const& noexcept(false)
    {
        if (mValue)
        {
            return *mValue;
        }
        throw std::runtime_error("current Result not contain a 'Value' ");
    }

    T&& ValueOrThrow() && noexcept(false)
    {
        if (mValue)
        {
            value_type* ptmp = mValue; /** todo refine me?? */
            mValue = nullptr;

            return std::move(*ptmp);
        }

        throw std::runtime_error("current Result not contain a 'Value' ");
    }

public:
    /**F: callable function type,
     * The Callable is expected to be compatible to this interface: T f(E const&);
     */
    template <typename F>
    T Resolve(F&& f) const
    {
        if (mValue)
        {
            return *mValue;
        }

        return f(*mErr); /** todo, if not contain value, is always have mErr?? */
    }

    template<template<class...> class Target, class T>
    struct is_template_of
    {
        static const bool value = false;
    };

    template<template<class...> class Target, class...Args>
    struct is_template_of<Target, Target<Args...>>
    {
        static const bool value = true;
    };

    /**
     * here have two type of callable function:
     * - Result<xxx, E> f(T const&)
     * - xxx f(T const&)
     */

    /** for the case of F  is: Result<xxx, E> f(T const&) */
    template<typename F, class = typename std::enable_if<is_template_of<Result, decltype(std::declval<F>()(T())) >::value >::type>
    auto Bind(F&& f) -> decltype(f(*this))
    {
        if (mValue)
        {
            return std::move(f(*mValue));
        }

        return std::move(Result(*mErr)); /** todo, if not contain value, is always have mErr?? */
    }

    /** for the case of F  is: xxx f(T const&), */
    template<typename F, class = typename std::enable_if<!is_template_of<Result, decltype(std::declval<F>()(T())) >::value >::type>
    auto Bind(F&& f) -> Result<decltype(f(*this)), E>
    {
        if (mValue)
        {
            return std::move(Result<decltype(f(*this)), E>(f(*mValue)));
        }

        return std::move(Result(*mErr)); /** todo, if not contain value, is always have mErr?? */
    }

private:
    T* mValue = nullptr;
    E* mErr = nullptr;
};


///////////////////////////////////////////////////////////////////////////////
// This section defines the interface of the ara::core::Result template 
// specialization where the type T is "void".
// This specialization omits these member functions that are defined in the 
// generic template:
//  - operator->
//  - Bind
// In addition, a number of function overloads collapse to a single, 
// no - argument one
///////////////////////////////////////////////////////////////////////////////

template <typename E>
class Result< void, E> final
{
public:
    using value_type = void;
    using error_type = E;

public:
    Result() noexcept
        :mHasValue(true)
    {
        /** todo something */
    }

    explicit Result(E const& e)
        :mErr(new error_type(e))
    {
        /** todo something */
    }

    explicit Result(E&& e)
        :mErr(new error_type(std::move(e)))
    {
        /** todo something */
    }

    Result(Result const& other)
    {
        if (other.mErr)
        {
            mErr = new error_type(other.mErr);
        }
    }

    Result(Result&& other) noexcept(std::is_nothrow_move_constructible<E>::value)
        :mErr(other.mErr)
    {
        other.mErr = nullptr;
    }

    ~Result()
    {
        if (mErr)
        {
            delete mErr;
        }
    }
public:
    static Result FromValue()
    {
        return std::move(Result());
    }

    static Result FromError(E const& e)
    {
        return std::move(Result(e));
    }

    static Result FromError(E&& e)
    {
         return std::move(Result(std::move(e)));
    }

    template <typename... Args>
    static typename std::enable_if<std::is_constructible<E, Args...>::value, Result>::type FromError(Args&&... args)
    {
        return std::move(Result(std::move(error_type(args...))));
    }

public:
    Result& operator= (Result const& other)
    {
        if(mErr)
        {
            delete mErr;
            mErr = nullptr;
        }

        if (other.mErr)
        {
            mErr = new error_type(other.mErr);
        }

        mHasValue = other.mHasValue;
    }

    Result& operator= (Result&& other) noexcept(std::is_nothrow_move_constructible< E >::value && 
                                                std::is_nothrow_move_assignable< E >::value)
    {
        if (mErr)
        {
            delete mErr;
        }

        mErr = other.mErr;
        other.mErr = nullptr;
        mHasValue = other.mHasValue;
        other.mHasValue = false; /** todo fixme?? */
        return *this;
    }

    template <typename... Args> /** todo refine me, is need check is_constructible ?? */
    typename std::enable_if<std::is_constructible<void, Args...>::value, void>::type EmplaceValue(Args&&... args) noexcept
    {
        /** todo something */
        mHasValue = true; /** todo fixme?? */
    }

    template <typename... Args>
    typename std::enable_if<std::is_constructible<E, Args...>::value, void>::type EmplaceError(Args&&... args)
    {
        if (mErr)
        {
            delete mErr;
        }

        mErr = new error_type(args...);
    }

    void Swap(Result& other) noexcept(std::is_nothrow_move_constructible< E >::value && 
                                      std::is_nothrow_move_assignable< E >::value)
    {
        error_type* tempErr = other.mErr;
        bool tempVal = other.mHasValue;
        other.mErr = mErr;
        other.mHasValue = mHasValue;
        mErr = tempErr;
        mHasValue = tempVal;
    }

public:
    bool HasValue() const noexcept
    {
        return mHasValue; /** todo refine me?? */
    }

    explicit operator bool() const noexcept
    {
        return mHasValue; /** todo refine me?? */
    }

    void operator * () const
    {
        /** todo something */
    }

    void Value() const
    {
        /** todo something */
    }

    E const& Error() const&
    {
        /**the spec says(so here we just do assert):
        * The behavior of this function is undefined if *this
        * does not contain a value.
        */
        assert(mErr != nullptr);

        return *mErr;
    }

    E&& Error()&&
    {
        /**the spec says(so here we just do assert):
         * The behavior of this function is undefined if *this
         * does not contain a value.
         */
        assert(mErr != nullptr);
        error_type* ptmp = mErr; /** todo refine me?? */
        mErr = nullptr;

        return std::move(*ptmp);
    }

    template <typename U>
    void ValueOr(U&& defaultValue) const
    {
        /** todo something */
    }

    template <typename G>
    E ErrorOr(G&& defaultError) const
    {
        if (mErr)
        {
            return *mErr;
        }

        return static_cast<error_type>(defaultError);
    }

    template <typename G>
    bool CheckError(G&& error) const
    {
        if (mErr)
        {
            return *mErr == static_cast<error_type>(error);
        }

        return false;
    }

    void ValueOrThrow() const noexcept(false)
    {

        if (mHasValue == false)
        {
            throw std::runtime_error("current Result not contain a 'Value' ");
        }
    }

    /**F: callable function type,
     * The Callable is expected to be compatible to this interface: T f(E const&);
     */
    template <typename F>
    void Resolve(F&& f) const
    {
        if (mHasValue == false)
        {
            f(*mErr); /** todo, if not contain a value, is always have mErr?? */
        }
    }
private:
    error_type* mErr = nullptr;
    bool mHasValue = false;
};

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
template <typename T, typename E>
bool operator== (Result< T, E > const& lhs, Result< T, E > const& rhs)
{
    return (lhs.HasValue() && rhs.HasValue()) && (lhs.Value() == rhs.Value());
}

template <typename T, typename E>
bool operator!= (Result< T, E > const& lhs, Result< T, E > const& rhs)
{
    return !(lhs == rhs);
}

template <typename T, typename E>
bool operator== (Result< T, E > const& lhs, T const& rhs)
{
    return lhs.HasValue() && (lhs.Value() == rhs);
}

template <typename T, typename E>
bool operator== (T const& lhs, Result< T, E > const& rhs)
{
return rhs.HasValue() && (lhs == rhs.Value());
}

template <typename T, typename E>
bool operator!= (Result< T, E > const& lhs, T const& rhs)
{
    return !(lhs == rhs);
}

template <typename T, typename E>
bool operator!= (T const& lhs, Result< T, E > const& rhs)
{
    return !(lhs == rhs);
}

template <typename T, typename E>
bool operator== (Result< T, E > const& lhs, E const& rhs)
{
    return  (!lhs.HasValue()) && (lhs.Error() == rhs);
}

template <typename T, typename E>
bool operator== (E const& lhs, Result< T, E > const& rhs)
{
    return (!rhs.HasValue()) && (rhs.Error() == lhs);
}

template <typename T, typename E>
bool operator!= (Result< T, E > const& lhs, E const& rhs)
{
    return !(lhs == rhs);
}

template <typename T, typename E>
bool operator!= (E const& lhs, Result< T, E > const& rhs)
{
    return !(lhs == rhs);
}

template <typename T, typename E>
void swap(Result< T, E >& lhs, Result< T, E >& rhs) noexcept(noexcept(lhs.Swap(rhs)))
{
    lhs.Swap(rhs);
}

} /** namespace core */
} /** namespace ara */
#endif /** !__ARA_CORE_RESULT_H__ */

